/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package chatsystem;

import java.io.File;

import chatsystem.observable.Informations;
import chatsystem.observable.ListeConversation;

import java.io.IOException;
import java.net.InetAddress;

import java.util.Observable;
import java.util.logging.Level;
import java.util.logging.Logger;
import protocol.*;

/**
 *
 * @author Maël
 */
public class Controller extends Observable {

    private ChatNI ni;
    private FileNI fni;
    private String localUser;
    private ListeUtilisateurs users;
    private String downloadsPath;
    private ListeConversation listConvModel;
    private Informations infos;
    //Utilisation du pattern singleton (car un seul Controller ne doit être instancié)
    private static boolean instance = false;

    public static synchronized Controller getInstance() throws Exception {
        if (!instance) {

            Controller.instance = true;
            return new Controller();

        } else {
            throw new Exception("Controller already instanciated.");
        }
    }

    private Controller() throws IOException, InterruptedException {

        this.users = new ListeUtilisateurs();

        this.fni = new FileNI(this.users);


        this.listConvModel = new ListeConversation();
        this.infos = new Informations();

        try {
            this.ni = new ChatNI(this);
        } catch (IOException ex) {
            Logger.getLogger(Controller.class.getName()).log(Level.SEVERE, null, ex);
        }

        //Création du répertoire de téléchargement
        File dldir = new File(System.getProperty("user.home") + "/ChatSystemDownloads");
        dldir.mkdirs();
        this.downloadsPath = dldir.getPath();

        System.out.println(downloadsPath);
    }

    public String getDownloadPath() {
        return this.downloadsPath;
    }

    /**
     * Connexion au chat, envoie un Hello en broadcast
     * @param username le username de l'utilisateur local
     */
    public void connect(String username) {
        try {
            this.localUser = username;
            ni.send(new Hello(true, username, InetAddress.getLocalHost()),
                    InetAddressUtil.getLocalBroadcast());

        } catch (IOException ex) {
            Logger.getLogger(Controller.class.getName()).log(Level.SEVERE, null, ex);
        }
        new Thread(ni).start();
    }

    /**
     * Déconnexion du chat, envoie un Bye en broadcast
     */
    public void disconnect() {

        try {
            ni.send(new Bye(localUser, InetAddress.getLocalHost()), InetAddressUtil.getLocalBroadcast());
            this.users.flush();
            this.ni.fermeture();
        } catch (IOException ex) {
            Logger.getLogger(Controller.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Envoie un message à dest
     * @param dest le nom du destinataire du message
     * @param mess le message
     */
    public void sendMessage(String dest, String mess) {
        try {
            try {
                this.ni.send(new ChatMessage(this.localUser, mess, InetAddress.getLocalHost()), this.users.getByName(dest));
                listConvModel.addConversationToList(dest, mess, true, false);
            } catch (NoSuchFieldException ex) {
                //Si on essaye d'envoyer un message à quelqu'un qui n'est plus connecté
                listConvModel.addConversationToList(dest, "Message \"" + mess + "\" not sent, " + dest + " is not connected", true, true);
            }

        } catch (IOException ex) {
            Logger.getLogger(Controller.class.getName()).log(Level.SEVERE, null, ex);
        }

    }

    /**
     * Traite les messages reçus, les oriente selon leur nature
     * @param m le message reçu
     * @param addr l'adresse IP de provenance du message
     */
    public void traiterMessage(Message m, InetAddress addr) {
        if (m.getClass().equals(Hello.class)) {
            traiterHello((Hello) m, addr);
        } else if (m.getClass().equals(Bye.class)) {
            /* Comme dans traiterBye on n'a pas l'objet Bye, on fait le test pour
             * Ne pas traiter son propre Bye
             * Et ne pas traiter le Bye de quelqu'un qui n'est pas dans la liste ici
             */
            if (!m.getUsername().equals(localUser)&&(this.users.contains(m.getUsername()))) {
                //TODO: voir comment gérer sans passer par le gui
                //this.gui.addInfo(m.getUsername() + " vient de partir...");
                this.infos.addInfo(m.getUsername()+" vient de partir...");
                traiterBye(addr);
            }
        } else if (m.getClass().equals(ChatMessage.class)) {
            traiterChatMessage((ChatMessage) m, addr);
        } else if (m.getClass().equals(FileNotify.class)) {
            traiterFileNotify((FileNotify) m, addr);
        } else if (m.getClass().equals(AckFile.class)) {
            traiterAckFile((AckFile) m, addr);
        }
    }

    /**
     * Traitement des Hello
     * @param mess le Hello
     * @param addr l'adresse de provenance du Hello
     */
    private void traiterHello(Hello mess, InetAddress addr) {
        System.out.println("je reçois un hello : "+mess);
        System.out.println("Mon username : " + localUser);
        if (!mess.getUsername().equals(this.localUser)) {
            if (users.contains(mess.getUsername())) {
                this.users.replaceUser(mess.getUsername(), addr);
            } else {
                this.users.addUser(mess.getUsername(), addr);
            }
            this.users.displaytoString();
            //Si Repreq est à true <=> la personne vient de se connecter
            if (mess.isRepreq()) {
                this.infos.addInfo(mess.getUsername() + " vient de se connecter!");
                try {
                    this.ni.send(new Hello(false, localUser, InetAddress.getLocalHost()), addr);
                } catch (IOException ex) {
                    Logger.getLogger(Controller.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
            //Sinon, la personne était déjà là
            else {
                this.infos.addInfo(mess.getUsername()+" est là!");
            }
        }

        // surement pas nécessaire : gui.addNewConnectedUser();
    }

    /**
     * Traitement des Bye
     * @param addr l'adresse IP de provenance du Bye
     */
    private void traiterBye(InetAddress addr) {
        this.users.removeUserByAddr(addr);
    }

    /**
     * Traitement des ChatMessage
     * @param m le message
     * @param addr l'adresse IP de provenance du message
     */
    private void traiterChatMessage(ChatMessage m, InetAddress addr) {
        Utilisateur u = this.users.getUserByAddr(addr);
        System.out.println(addr);
        System.out.println("TEST 2 ===> " + this.users.getUserByAddr(addr).getUserName());
        //this.gui.addMessToConversation(users.getUserByAddr(addr).getUserName(), m.getMessage(), false);
        listConvModel.addConversationToList(users.getUserByAddr(addr).getUserName(), m.getMessage(), false, false);
    }

    /**
     * Traitement des FileNotify
     */
    private void traiterFileNotify(FileNotify m, InetAddress addr) {
        this.fni.addReceiving(addr, new File(this.downloadsPath + '/' + m.getNomFichier()));
    }

    /**
     * Traitement des AckFile
     */
    private void traiterAckFile(AckFile m, InetAddress addr) {
        if (m.isAck()) {
            try {
                this.fni.startSending(addr);
            } catch (IOException ex) {
                Logger.getLogger(Controller.class.getName()).log(Level.SEVERE, null, ex);
            }
        } else {
            this.fni.removeSending(addr);
        }
    }

    /**
     * Méthode pour fermer les sockets
     */
    public void fermeture() {
        this.ni.fermeture();
    }

    /**
     * @return la liste des utilisateurs
     */
    public ListeUtilisateurs getListeUsers() {
        return this.users;
    }

    public ListeConversation getListConvModel() {
        return this.listConvModel;
    }

    public Informations getInfos() {
        return this.infos;
    }
    
    public FileNI getFileManager() {
        return this.fni;
    }
    

    /**
     * Envoi d'un fichier
     * @param file le fichier
     * @param username le destinataire du fichier
     */
    public void sendFile(File file, String username) {

        try {
            this.fni.addSending(users.getByName(username), file);
            this.ni.send(
                    new FileNotify(this.localUser, InetAddress.getLocalHost(), file.getName()),
                    this.users.getByName(username));
        } catch (IOException ex) {
            System.out.println("Envoi impossible");
            Logger.getLogger(Controller.class.getName()).log(Level.SEVERE, null, ex);
        } catch (NoSuchFieldException ex) {
            System.out.println(username + " is not connected anymore.");
            Logger.getLogger(Controller.class.getName()).log(Level.SEVERE, null, ex);
        }

    }

    /**
     * Pour recevoir un fichier
     * @param username nom de l'émetteur du fichier
     */
    public void receiveFile(String username) {
        try {
            InetAddress sender = this.users.getByName(username);
            fni.startReceiving(sender);
            ni.send(new AckFile(localUser, InetAddress.getLocalHost(), true), sender);
        } catch (NoSuchFieldException ex) {
            Logger.getLogger(Controller.class.getName()).log(Level.SEVERE, null, ex);
            System.out.println("user not found");
        } catch (IOException ex) {
            System.out.println("impossible to receive the file");
            Logger.getLogger(Controller.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Pour refuser l'envoi d'un fichier
     * @param username nom de l'émetteur du fichier à qui on doit envoyer le refus
     */
    public void refuseFile(String username) {
        try {
            InetAddress sender = this.users.getByName(username);
            fni.removeReceiving(sender);
            ni.send(new AckFile(localUser, InetAddress.getLocalHost(), false), sender);
        } catch (IOException ex) {
            Logger.getLogger(Controller.class.getName()).log(Level.SEVERE, null, ex);
        } catch (NoSuchFieldException ex) {
            Logger.getLogger(Controller.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}
